#include "EventHandler.h"
#include "NetworkHandler.h"
#include "ServerHandler.h"
#include "ScreenHandler.h"
#include "WorldHandler.h"
#include "Config.h"

#include <cstdlib>

#include "MovingObject.h"
#include "PlayerObject.h"

#define FIREBALL_COOLDOWN 1500
#define FROSTBALL_COOLDOWN 750
#define MELEE_COOLDOWN 500

EventHandler globalEventHandler;

CustomEventReceiver* EventHandler::getEventReceiver()
{
	return &eventReceiver;
}

EventHandler::EventHandler()
{
	
}

void EventHandler::handleEvents()
{
	handleMouseEvents();	
	handleKeyEvents();
	globalCSHandler->handleReceivedMessages();
}

void EventHandler::handleMouseEvents() 
{
	if(globalWorldHandler.getPlayerCount() < 1)
	{
		return;
	}
	
	scene::ISceneManager* smgr = globalScreenHandler.getSmgr();
	
	MouseState* mouseState = eventReceiver.getMouseState();
	scene::ISceneCollisionManager* collisionmgr = smgr->getSceneCollisionManager();
	const core::line3df ray = collisionmgr->getRayFromScreenCoordinates(mouseState->Position, smgr->getActiveCamera());
	scene::ISceneNode* collidedNode = collisionmgr->getSceneNodeFromRayBB(ray);

	//higlight mouse over
	if(collidedNode != NULL)
	{
		std::map<int, MovingObject*> movingIDMap = globalWorldHandler.getMovingIDMap();
		std::map<int, MovingObject*>::iterator it = movingIDMap.find(collidedNode->getID());
		if(it != movingIDMap.end())
		{
			WorldObject* worldObject = static_cast<WorldObject*>(it->second);
			if(worldObject->getObjectType() != ATTACK_OBJECT
			&& worldObject->getObjectType() != PLAYER_OBJECT
			&& worldObject->getObjectType() != WORLD_OBJECT)
			{
				globalScreenHandler.highlightNode(collidedNode);
			}
		}
	}
	
	static bool compassClicked;
	if(mouseState->LeftButtonDown == false)
	{
		compassClicked = false;
		mouseState->LeftButtonEventHandled = false;
	}
	
	if(mouseState->LeftButtonDown
	|| mouseState->RightButtonDown)
	{
		scene::ITriangleSelector* selector = globalScreenHandler.getTerrainTriangleSelector();
				
		core::vector3df clickPoint;
		core::triangle3df triangle;
		//const core::line3df ray = collisionmgr->getRayFromScreenCoordinates(mouseState->Position, smgr->getActiveCamera());
		const scene::ISceneNode* node = 0;
		
		collisionmgr->getCollisionPoint(ray, selector, clickPoint, triangle, node);

		std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
		PlayerObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
		if(playerObject->getIsDead() == false)
		{
			playerObject->faceTarget(clickPoint);		
		}
		
		if(clickPoint != core::vector3df(0,0,0)
		&& !eventReceiver.isKeyDown(KEY_KEY_S))
		{			
			scene::ISceneNode* clickedNode = collisionmgr->getSceneNodeFromRayBB(ray);
				
			if(COMPASSID == clickedNode->getID()
			&& mouseState->LeftButtonDown
			&& mouseState->LeftButtonEventHandled == false)
			{
				//hackers on steroids

				if(globalConfig.getIsServer() == true)
				{
					if(globalWorldHandler.getActiveRandomZoneObject() == NULL)
					{
						reinterpret_cast<ServerHandler*>(globalCSHandler)->handleReceivedRequestSeedAndTP();
					}
					else
					{
							reinterpret_cast<ServerHandler*>(globalCSHandler)->handleReceivedRequestDismantle();
					}
				}
				else
				{
					std::string temp = "";
					if(globalWorldHandler.getActiveRandomZoneObject() == NULL)
					{
						globalWorldHandler.setRandomSeed(WANT_SERVERSEED);

						globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(&temp), REQUESTSEEDANDTP);
					}
					else
					{
						globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(&temp), REQUESTDISMANTLE);
					}
				}
				mouseState->LeftButtonEventHandled = true;
				compassClicked = true;
			}
			else if(globalWorldHandler.getPlayerObjectsCount() > globalWorldHandler.getLocalPlayerIndex()
			&& compassClicked == false
			&& mouseState->LeftButtonEventHandled == false)
			{
				core::vector3df destPos = core::vector3df(clickPoint.X, clickPoint.Y+GROUNDHEIGHT, clickPoint.Z);
				if(playerObject->getIsDead() == false)
				{
					MovementInfo* oldMovementInfo = playerObject->getMovementInfo();
					playerObject->setMovementInfo(core::vector3df(0,0,0), destPos, oldMovementInfo->moveSpeed, 0.f, 0.f);
					playerObject->setStartMoving(true);
				}
			}
		}
	}
	
	f32 mouseDelta = eventReceiver.getMouseState()->mouseDelta;
	if(0 != mouseDelta)
	{
		globalScreenHandler.setCurrentZoom(mouseDelta);
		eventReceiver.getMouseState()->mouseDelta = 0;
	}
}

void EventHandler::handleKeyEvents() 
{
	if(eventReceiver.isKeyDown(KEY_F12))
	{
		IrrlichtDevice* device = globalScreenHandler.getDevice();
		device->closeDevice();
		eventReceiver.setKeyDown(KEY_F12, false);
	}
	
	if(eventReceiver.isKeyDown(KEY_F10))
	{
		if(globalConfig.getIsServer() == true
		&& globalWorldHandler.getActiveRandomZoneObject() != NULL)
		//&& globalWorldHandler.getAreAllPlayersDead() == true)
		{
			globalWorldHandler.dismantleRandomZone();
			globalWorldHandler.setRandomSeed(0);
			std::string temp = "";
			globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(&temp), GRANTDISMANTLE);
		}
		eventReceiver.setKeyDown(KEY_F10, false);
	}
	
	if(eventReceiver.isKeyDown(KEY_RETURN)
	&& !eventReceiver.isKeyHandled(KEY_RETURN))
	{
		
		if(globalScreenHandler.getIsTyping() == true)
		{
			globalScreenHandler.setIsTyping(false);
			globalScreenHandler.hideInputBox();
			
			const wchar_t* chatInput= globalScreenHandler.getInputBoxContents();
			size_t inputLen = wcslen(chatInput); // Null-terminator
			char  convertedInput[inputLen + 1];
			wcstombs(convertedInput, chatInput, inputLen);
			convertedInput[inputLen] = '\0';
			std::string q = convertedInput;
			
			if(globalConfig.getIsServer() == true)
			{
				globalScreenHandler.appendToChatLog(globalConfig.getLocalPlayerName()+ ": " + q.substr(0, q.find("\n")) + "\n");
			}
			else
			{
				globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(&q), CHATMESSAGE);
			}
		}
		else
		{
			globalScreenHandler.showInputBox();
			globalScreenHandler.setIsTyping(true);
		}
		
		eventReceiver.setKeyHandled(KEY_RETURN, true);
	}
	
	//TODO: merge and remove copypaste code
	
	if(eventReceiver.isKeyDown(KEY_KEY_1))
	{
		static f32 last;
		f32 now = globalScreenHandler.getDevice()->getTimer()->getTime();
		if(last == 0
		|| (now - last) > MELEE_COOLDOWN)
		{
		   	std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
		   	PlayerObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
			if(playerObject->getIsDead() == false)
			{
			   	playerObject->setAnimation(scene::EMAT_ATTACK, false);

				std::string name = globalConfig.getLocalPlayerName();	   	
				AttackObject* projectileObject = globalWorldHandler.setupAttack(playerObject->getPosition(), playerObject->getNode()->getRelativeTransformation(), MELEE, true, name);
				if(globalConfig.getIsServer() == false)
				{
					globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(projectileObject), ATTACKOBJECT);
				}
				eventReceiver.setKeyDown(KEY_KEY_1, false);
			}
			last = now;
		}
	}
	
	if(eventReceiver.isKeyDown(KEY_KEY_2))
	{
		static f32 last;
		f32 now = globalScreenHandler.getDevice()->getTimer()->getTime();
		if(last == 0
		|| (now - last) > FROSTBALL_COOLDOWN)
		{
		   	std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
		   	PlayerObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
			if(playerObject->getIsDead() == false)
			{
			   	playerObject->setAnimation(scene::EMAT_ATTACK, false);

				std::string name = globalConfig.getLocalPlayerName();	   			
				AttackObject* projectileObject = globalWorldHandler.setupAttack(playerObject->getPosition(), playerObject->getNode()->getRelativeTransformation(), FROSTBALL, true, name);
				if(globalConfig.getIsServer() == false)
				{
					globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(projectileObject), ATTACKOBJECT);
				}
		
				eventReceiver.setKeyDown(KEY_KEY_2, false);
			}
			last = now;
		}
	}
	
	if(eventReceiver.isKeyDown(KEY_KEY_3))
	{
		static f32 last;
		f32 now = globalScreenHandler.getDevice()->getTimer()->getTime();
		if(last == 0
		|| (now - last) > FIREBALL_COOLDOWN)
		{
		   	std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
		   	PlayerObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
			if(playerObject->getIsDead() == false)
			{
			   	playerObject->setAnimation(scene::EMAT_ATTACK, false);

				std::string name = globalConfig.getLocalPlayerName();	   	
				AttackObject* projectileObject = globalWorldHandler.setupAttack(playerObject->getPosition(), playerObject->getNode()->getRelativeTransformation(), FIREBALL, true, name);
				if(globalConfig.getIsServer() == false)
				{
					globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(projectileObject), ATTACKOBJECT);
				}
				eventReceiver.setKeyDown(KEY_KEY_3, false);
			}
			last = now;
		}
	}
#if 0
	if(eventReceiver.isKeyDown(KEY_KEY_3))
	{
		std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
		MovingObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
//		reinterpret_cast<scene::IAnimatedMeshSceneNode*>(playerObject->getNode())->setMD2Animation(scene::EMAT_ATTACK);
		playerObject->setAnimation(scene::EMAT_ATTACK, false);
		eventReceiver.setKeyDown(KEY_KEY_3, false);
	}
#endif
	if(eventReceiver.isKeyDown(KEY_KEY_S) == true
	&& eventReceiver.isKeyHandled(KEY_KEY_S) == false)
	{

			std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
			MovingObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
			playerObject->setIsMoving(false);
			playerObject->setStartMoving(false);
			//reinterpret_cast<scene::IAnimatedMeshSceneNode*>(playerObject->getNode())->setMD2Animation(scene::EMAT_STAND);
			if(playerObject->getAnimation() == scene::EMAT_RUN)
			{
				playerObject->setAnimation(scene::EMAT_STAND, true);	
			}
			MouseState* mouseState = eventReceiver.getMouseState();
			if(mouseState->LeftButtonDown == true)
			{
				eventReceiver.setLeftButtonEventHandled(true);
			}
			if(mouseState->RightButtonDown == true)
			{
				eventReceiver.setRightButtonEventHandled(true);
			}
			eventReceiver.setKeyHandled(KEY_KEY_S, true);
		//}
		//eventReceiver.setKeyDown(KEY_KEY_S, false);
		//eventReceiver.setKeyDown(KEY_KEY_H, false);
	}
	
	
#if 0
	//Self-Ress code 
	if(eventReceiver.isKeyDown(KEY_KEY_5) == true
	&& eventReceiver.isKeyHandled(KEY_KEY_5) == false)
	{
		std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
		PlayerObject* playerObject = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
		


		if(globalConfig.getIsServer() == true)
		{
			playerObject->setIsDead(false);
			playerObject->setAnimation(scene::EMAT_STAND, true);
		}
		else
		{
			std::string empty = "";
			globalCSHandler->addToSendQueue(reinterpret_cast<uintptr_t>(&empty), REQUESTRESS);
		}
		eventReceiver.setKeyHandled(KEY_KEY_5, true);
	}
#endif
}


bool EventHandler::handleCollisionEvent(WorldObject* object1, WorldObject* object2, bool* object1Removed)
{	
	unsigned int type1 = object1->getObjectType();
	unsigned int type2 = object2->getObjectType();
	if(object1 == object2
	|| (type2 == PLAYER_OBJECT && reinterpret_cast<PlayerObject*>(object2)->getIsConnected() == false)
	//|| (type1 == PLAYER_OBJECT && reinterpret_cast<PlayerObject*>(object1)->getName() == globalConfig.getLocalPlayerName())
	|| (type1 == PLAYER_OBJECT && type2 == PLAYER_OBJECT)
	|| (type1 == ENEMY_OBJECT && type2 == ENEMY_OBJECT && reinterpret_cast<EnemyObject*>(object2)->getIsMoving() == true)
	|| (type1 == ATTACK_OBJECT && type2 == PLAYER_OBJECT 
		&& reinterpret_cast<AttackObject*>(object1)->getOriginatingPlayer() 
		== reinterpret_cast<PlayerObject*>(object2)->getName()))
	{
		return false;
	}


		switch(type1)
		{
			case ATTACK_OBJECT:
				if(globalConfig.getIsServer() == true)
				{
					handleAttackCollision(static_cast<AttackObject*>(object1), object2, object1Removed);
				}
				else
				{
					globalWorldHandler.removeWorldObject(object1);
				}
				*object1Removed = true;
			break;
		}
	
		#if 0
		if(worldObjects.at(i)->getObjectType() == PLAYER_OBJECT)
		{
			std::string name = reinterpret_cast<AttackObject*>(movingObject)->getOriginatingPlayer();
			if(name != *reinterpret_cast<PlayerObject*>(worldObjects.at(i))->getName())
			{
				globalScreenHandler.appendToChatLog("[COLLISION]: " + *reinterpret_cast<PlayerObject*>(worldObjects.at(i))->getName() + " was hit for massive damage!\n");
				removeWorldObject(movingObject);
				*object1Removed = true;
			}
		}
	#endif

	return true;
}

void EventHandler::handleAttackCollision(AttackObject* projectile, WorldObject* targetObject, bool* projectileRemoved)
{
	switch(targetObject->getObjectType())
	{
		case PLAYER_OBJECT:
		{
			if(reinterpret_cast<PlayerObject*>(targetObject)->getIsDead() == false)
			{
				std::string name = reinterpret_cast<AttackObject*>(projectile)->getOriginatingPlayer();
				if(name == "enemy") //reinterpret_cast<PlayerObject*>(targetObject)->getName())
				{
					reinterpret_cast<LivingObject*>(targetObject)->setIsDead(true);
					reinterpret_cast<PlayerObject*>(targetObject)->setKillCount(0);
					//globalScreenHandler.appendToChatLog("[COLLISION]: " + reinterpret_cast<PlayerObject*>(targetObject)->getName() + " was hit!\n");
					
					if(globalWorldHandler.getAreAllPlayersDead() == true)
					{
						std::vector<EnemyObject*> enemyObjects = globalWorldHandler.getEnemyObjects();
						globalScreenHandler.appendToChatLog("[SERVER]: RIP5never. Host can press F10 to reset.\n");
						globalWorldHandler.setAdditionalEnemyCount(0);
					}
					globalWorldHandler.removeWorldObject(projectile);
				}
				
			}
		}
		break;
		case ENEMY_OBJECT:
		case LIVING_OBJECT:
			if(reinterpret_cast<LivingObject*>(targetObject)->getIsDead() == false
			&& projectile->getOriginatingPlayer() != "enemy")
			{
				PlayerObject* player;
				std::string name = projectile->getOriginatingPlayer();		
				if(name == globalConfig.getLocalPlayerName())
				{
					std::vector<PlayerObject*> playerObjects = globalWorldHandler.getPlayerObjects();
					player = playerObjects[globalWorldHandler.getLocalPlayerIndex()];
				}
				else
				{
					player = globalCSHandler->getPlayerObjectFromName(name);
				}
				
				if(player != NULL)
				{
					player->increaseKillCount();
				}
				reinterpret_cast<LivingObject*>(targetObject)->setIsDead(true);
				
				globalWorldHandler.decreaseAliveEnemiesCount();
				if(globalWorldHandler.getAliveEnemiesCount() == 0)
				{
					globalWorldHandler.setIsCompassObjectActive(true);
				}
				
				globalWorldHandler.removeWorldObject(projectile);
			}
		break;
	}
}
